package com.simpou.commons.utils.model;

import com.simpou.commons.utils.reflection.Casts;
import com.simpou.commons.utils.validation.Assertions;


/**
 * Habilita o retorno de múltiplos objetos por um método. Pode ser usado também
 * para passar múltiplos parâmetros a um método caso o mesmo requeira muitos
 * deles.
 *
 * Não é uma boa prática usar em métodos públicos já que é necessário
 * estabelecer um contrato via javadoc que especifica como resgatar o objeto
 * desejado dentre os vários retornados, e isto não utiliza tipagem segura. Além
 * disto a tipagem do objeto desejado é desconhecida em runtime, podendo gerar
 * vários ClassCastException, durante operações de get.
 *
 * @author Jonas Pereira
 * @since 2012-10-17
 * @version 2013-06-01
 */
public final class MultipleObjects {
    /**
     * Representação dos múltiplos Objetos. São identificados pela posição.
     */
    private final Object[] objects;

    /**
     * Melhor usar privado neste caso e usar factories. Várias formas de
     * instanciação podem ser adicionadas usando-se os mesmos argumentos de
     * método e variando-se seu nome.
     *
     * @param objects
     *            Objetos de retorno.
     */
    private MultipleObjects(final Object... objects) {
        this.objects = Assertions.notEmpty(objects,
                "At least one object is required.");
    }

    /**
     * Cria um novo objeto múltiplo usando-se todos parâmetros na ordem em que
     * foram informados.
     *
     * <b>Não é uma boa prática usar em métodos públicos.</b>
     *
     * @param objects
     *            Objetos de retorno. A ordem em que são passados os elementos
     *            determina a posição de retorno em operações get,sendo que o
     *            primeiro parâmetro é considerado estar na posição zero e assim
     *            sucessivamente.
     * @return Objeto múltiplo. Os parâmetros são ordenados na forma como foram
     *         informados.
     */
    public static MultipleObjects fromAll(final Object... objects) {
        return new MultipleObjects(objects);
    }

    /**
     * @return Quantidade de objetos armazenados.
     */
    public int length() {
        return this.objects.length;
    }

    /**
     * @return Primeiro objeto.
     */
    public Object getFirst() {
        return this.objects[0];
    }

    /**
     * @return Último objeto.
     */
    public Object getLast() {
        return this.objects[this.objects.length-1];
    }

    /**
     * @param position
     *            Posição do objeto desejado.
     * @return Objeto desejado.
     */
    public Object get(final int position) {
        Assertions.inRange(position, 0, this.objects.length);

        return this.objects[position];
    }

    /**
     * @param objClass
     *            Tipo do objeto desejado.
     * @return Primeiro objeto.
     */
    public <T> T getFirst(final Class<T> objClass) {
        return Casts.objCast(objClass, getFirst());
    }

    /**
     * @param objClass
     *            Tipo do objeto desejado.
     * @return Último objeto.
     */
    public <T> T getLast(final Class<T> objClass) {
        return Casts.objCast(objClass, getLast());
    }

    /**
     * @param objClass
     *            Tipo do objeto desejado.
     * @param position
     *            Posição do objeto desejado.
     * @return Objeto desejado.
     */
    public <T> T get(final Class<T> objClass, final int position) {
        return Casts.objCast(objClass, get(position));
    }

    /**
     * @return Cópia de todos objetos armazenados.
     */
    public Object[] getObjects() {
        return this.objects.clone();
    }
}
